#include <stdio.h>
#include <stdint.h>
#include "FLOAT.h"
#include <sys/mman.h>

extern char _vfprintf_internal;
extern char _fpmaxtostr;
extern char _ppfs_setargs;
extern int __stdio_fwrite(char *buf, int len, FILE *stream);

__attribute__((used)) static int format_FLOAT(FILE *stream, FLOAT f) {
	/* TODO: Format a FLOAT argument `f' and write the formating
	 * result to `stream'. Keep the precision of the formating
	 * result with 6 by truncating. For example:
	 *              f          result
	 *         0x00010000    "1.000000"
	 *         0x00013333    "1.199996"
	 */
	char buf[80];
	int sign = f & (1 << 31);
	if(sign) f = (~f) + 1; //负数变正方便计算数值，输出时再加上负号即可
	int frac = 0;//小数位
	int i;
	int base=100000000;//Accuracy
	for (i = 15; i >= 0;i--){
		base >>= 1;
		if (f&(1<<i))  frac += base;
	}
	while(frac > 999999) frac /= 10;//截断为六位小数
	int Integer = f >> 16; //整数位
	int len = 0;
	if(sign) len = sprintf(buf, "-%d.%06d",Integer,frac);
	else len = sprintf(buf, "%d.%06d",Integer,frac);
	return __stdio_fwrite(buf, len, stream);
}

static void modify_vfprintf() {
	/* TODO: Implement this function to hijack the formating of "%f"
	 * argument during the execution of `_vfprintf_internal'. Below
	 * is the code section in _vfprintf_internal() relative to the
	 * hijack.
	 */
	
	int vfprintf_addr = (int)(& _vfprintf_internal);
	int call_offset = 0x306;//call相对于_vfprintf_internal函数的偏移量
	//设置可写权限
	//mprotect((void*)((vfprintf_addr + call_offset - 100) & 0xfffff000), 4096*2, PROT_READ | PROT_WRITE | PROT_EXEC);
	//修改call要跳转的函数为format_FLOAT
	int* addr_after_call = (int*)(vfprintf_addr + call_offset + 1);//操作4字节的地址用int*
	*addr_after_call += (int)format_FLOAT-(int)(& _fpmaxtostr);
	
	char* one_byte;//操作一个字节用char*
	//地址最高的参数是第一个参数stream不用修改，直接从第二个参数开始修改

	//第二个参数
	one_byte = (char*)(vfprintf_addr + call_offset - 0xa);
	*one_byte = 0xff;//opcode:push m32 (第二个参数argptr存在esp里)
	one_byte = (char*)(vfprintf_addr + call_offset - 0x9);
	*one_byte = 0x32;//ModR/M: 00 110 010 
	one_byte = (char*)(vfprintf_addr + call_offset - 0x8);
	*one_byte = 0x90;//nop 原来fstpt一共用了3个字节，多出的一个字节置空

	//修改前一条用于分配栈空间大小的指令sub(多了一个push要维护栈)
	one_byte = (char*)(vfprintf_addr + call_offset - 0xb);
	*one_byte = 0x08;//sub 0x8,%esp
	//用nop清除其他浮点指令
	one_byte = (char*)(vfprintf_addr + call_offset - 0x22);
	*one_byte = 0x90;//fldt -> nop
	one_byte = (char*)(vfprintf_addr + call_offset - 0x21);
	*one_byte = 0x90;//(%edx) -> nop

	one_byte = (char*)(vfprintf_addr + call_offset - 0x1e);
	*one_byte = 0x90;//fldl -> nop
	one_byte = (char*)(vfprintf_addr + call_offset - 0x1d);
	*one_byte = 0x90;//(%edx) -> nop



#if 0
	else if (ppfs->conv_num <= CONV_A) {  /* floating point */
		ssize_t nf;
		nf = _fpmaxtostr(stream,
				(__fpmax_t)
				(PRINT_INFO_FLAG_VAL(&(ppfs->info),is_long_double)
				 ? *(long double *) *argptr
				 : (long double) (* (double *) *argptr)),
				&ppfs->info, FP_OUT );
		if (nf < 0) {
			return -1;
		}
		*count += nf;

		return 0;
	} else if (ppfs->conv_num <= CONV_S) {  /* wide char or string */
#endif

	/* You should modify the run-time binary to let the code above
	 * call `format_FLOAT' defined in this source file, instead of
	 * `_fpmaxtostr'. When this function returns, the action of the
	 * code above should do the following:
	 */

#if 0
	else if (ppfs->conv_num <= CONV_A) {  /* floating point */
		ssize_t nf;
		nf = format_FLOAT(stream, *(FLOAT *) *argptr);
		if (nf < 0) {
			return -1;
		}
		*count += nf;

		return 0;
	} else if (ppfs->conv_num <= CONV_S) {  /* wide char or string */
#endif

}

static void modify_ppfs_setargs() {
	/* TODO: Implement this function to modify the action of preparing
	 * "%f" arguments for _vfprintf_internal() in _ppfs_setargs().
	 * Below is the code section in _vfprintf_internal() relative to
	 * the modification.
	 */
	//在float分支开头放jmp跳到long long分支
	int ppfs_setargs_addr = (int)(& _ppfs_setargs);
	int float_offset = 0x74;
	char* one_type;
	//修改float分支前一条指令为jmp
	one_type = (char*)(ppfs_setargs_addr + float_offset - 3);
	*one_type = 0xeb;
	one_type = (char*)(ppfs_setargs_addr + float_offset - 2);
	*one_type = 0x30;
	one_type = (char*)(ppfs_setargs_addr + float_offset - 1);
	*one_type = 0x90;  //nop

#if 0
	enum {                          /* C type: */
		PA_INT,                       /* int */
		PA_CHAR,                      /* int, cast to char */
		PA_WCHAR,                     /* wide char */
		PA_STRING,                    /* const char *, a '\0'-terminated string */
		PA_WSTRING,                   /* const wchar_t *, wide character string */
		PA_POINTER,                   /* void * */
		PA_FLOAT,                     /* float */
		PA_DOUBLE,                    /* double */
		__PA_NOARG,                   /* non-glibc -- signals non-arg width or prec */
		PA_LAST
	};

	/* Flag bits that can be set in a type returned by `parse_printf_format'.  */
	/* WARNING -- These differ in value from what glibc uses. */
#define PA_FLAG_MASK		(0xff00)
#define __PA_FLAG_CHAR		(0x0100) /* non-gnu -- to deal with hh */
#define PA_FLAG_SHORT		(0x0200)
#define PA_FLAG_LONG		(0x0400)
#define PA_FLAG_LONG_LONG	(0x0800)
#define PA_FLAG_LONG_DOUBLE	PA_FLAG_LONG_LONG
#define PA_FLAG_PTR		(0x1000) /* TODO -- make dynamic??? */

	while (i < ppfs->num_data_args) {
		switch(ppfs->argtype[i++]) {
			case (PA_INT|PA_FLAG_LONG_LONG):
				GET_VA_ARG(p,ull,unsigned long long,ppfs->arg);
				break;
			case (PA_INT|PA_FLAG_LONG):
				GET_VA_ARG(p,ul,unsigned long,ppfs->arg);
				break;
			case PA_CHAR:	/* TODO - be careful */
				/* ... users could use above and really want below!! */
			case (PA_INT|__PA_FLAG_CHAR):/* TODO -- translate this!!! */
			case (PA_INT|PA_FLAG_SHORT):
			case PA_INT:
				GET_VA_ARG(p,u,unsigned int,ppfs->arg);
				break;
			case PA_WCHAR:	/* TODO -- assume int? */
				/* we're assuming wchar_t is at least an int */
				GET_VA_ARG(p,wc,wchar_t,ppfs->arg);
				break;
				/* PA_FLOAT */
			case PA_DOUBLE:
				GET_VA_ARG(p,d,double,ppfs->arg);
				break;
			case (PA_DOUBLE|PA_FLAG_LONG_DOUBLE):
				GET_VA_ARG(p,ld,long double,ppfs->arg);
				break;
			default:
				/* TODO -- really need to ensure this can't happen */
				assert(ppfs->argtype[i-1] & PA_FLAG_PTR);
			case PA_POINTER:
			case PA_STRING:
			case PA_WSTRING:
				GET_VA_ARG(p,p,void *,ppfs->arg);
				break;
			case __PA_NOARG:
				continue;
		}
		++p;
	}
#endif

	/* You should modify the run-time binary to let the `PA_DOUBLE'
	 * branch execute the code in the `(PA_INT|PA_FLAG_LONG_LONG)'
	 * branch. Comparing to the original `PA_DOUBLE' branch, the
	 * target branch will also prepare a 64-bit argument, without
	 * introducing floating point instructions. When this function
	 * returns, the action of the code above should do the following:
	 */

#if 0
	while (i < ppfs->num_data_args) {
		switch(ppfs->argtype[i++]) {
			case (PA_INT|PA_FLAG_LONG_LONG):
			here:
				GET_VA_ARG(p,ull,unsigned long long,ppfs->arg);
				break;
			// ......
				/* PA_FLOAT */
			case PA_DOUBLE:
				goto here;
				GET_VA_ARG(p,d,double,ppfs->arg);
				break;
			// ......
		}
		++p;
	}
#endif

}

void init_FLOAT_vfprintf() {
	modify_vfprintf();
	modify_ppfs_setargs();
}
